package com.itheima.util;

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnel;
import com.google.common.hash.PrimitiveSink;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 * @Author: HuLiang
 * @Date: 2024/07/11/9:13
 * @Description: 通用布隆过滤器 (Guava) 工具类。
 * <p>这个工具类对于某个类的对象过滤器有两种初始化方式</p>
 * <p>1、可以直接 {@link #put(Object)}，这个方式会自动寻找 map < Object.class,BloomFilter> （如果没有的话就会自动创建一个， Funnel 传递的是 hashCode()）</p>
 * <p>2、可以调用 {@link #newBloomFilter(Class, Funnel, Integer, Double)} 指定你需要的参数（推荐） </p>
 * <p><p>
 *     创建完成后可以使用{@link #put(Object)} 和 {@link #put(Class, Object)} 放入对象
 *     同样的也有 {@link #mightContain(Object)} 和 {@link #mightContain(Class, Object)} 判断对象是否存在
 * </p>
 * <p>否则会在添加数据到过滤器时自动创建一个以 hashCode() 为参数的过滤器</p>
 */
public class BloomFilterUtil {
    private final static Map<Class<?>,BloomFilter<Object>> bloomFilterMap = new HashMap<>();

    private static Integer DEFAULT_MAX_SIZE = 2 << 10;
    private static Double DEFAULT_FPP = 0.01;

    public static boolean isFilterExist(Class<?> clazz){
        return bloomFilterMap.get(clazz) != null;
    }
    /**
    * @date: 2024/7/11
    * @description: 直接使用当前对象类作为 key 和 value，批量插入 valueList
    */
    public static <T> void putAll(List<T> list){
        if(list == null)
            throw new IllegalArgumentException("无法插入空的 list");
        Class<?> key = list.get(0).getClass();
        if(bloomFilterMap.get(key) == null)
            newBloomFilter(key,newFunnel(),DEFAULT_MAX_SIZE, DEFAULT_FPP);
        BloomFilter filter = bloomFilterMap.get(key);

        for(T t:list)
            filter.put(t);

    }

    public static Integer getDefaultMaxSize() {
        return DEFAULT_MAX_SIZE;
    }

    public static void setDefaultMaxSize(Integer defaultMaxSize) {
        if(defaultMaxSize <= 0)
            throw new IllegalArgumentException("默认大小需要大于 0");
        DEFAULT_MAX_SIZE = defaultMaxSize;
    }

    /**
    * @author: HuLiang
    * @date: 2024/7/11
     * @param clazz 类型
     * @param funnel 哈希参数函数
     * @param size 位数
     * @param fpp 允许误判率
    * @return : void
    * @description:
    */


    public static void newBloomFilter(Class<?> clazz, Funnel funnel, Integer size, Double fpp){
        if (size == null || size <= 0) {
            throw new IllegalArgumentException("容量必须为正数");
        }
        if (clazz == null || funnel == null) {
            throw new NullPointerException("Class 和 Funnel 不能为空");
        }
        if (fpp == null || fpp <= 0 || fpp >= 1) {
            throw new IllegalArgumentException("误判率必须介于0和1之间");
        }
        BloomFilter filter = BloomFilter.create(funnel,size,fpp);

        bloomFilterMap.put(clazz,filter);


    }

    private static <T> Funnel<T> newFunnel(){
        return new Funnel<T>() {
            @Override
            public void funnel(T t, PrimitiveSink primitiveSink) {
                primitiveSink.putInt(t.hashCode());
            }
        };
    }
    /**
    * @date: 2024/7/11
    * @description: 直接使用当前对象类作为 key 和 value
    */
    public static <T> void put(T keyAndValue){
        if(keyAndValue == null)
            throw new IllegalArgumentException("keyAndValue 不允许为 null");

        Class<?> clazz = keyAndValue.getClass();
        if(bloomFilterMap.get(clazz) == null)
            newBloomFilter(clazz,newFunnel(),DEFAULT_MAX_SIZE, DEFAULT_FPP);

        bloomFilterMap.get(clazz).put(keyAndValue);
    }
    /**
    * @date: 2024/7/11
    * @description: 指定 key 类， value 值
    */
    public static <T> void put(Class<?> key,T value){
        if(value == null || key == null)
            throw new IllegalArgumentException("key 和 value 不允许为 null");

        if(bloomFilterMap.get(key) == null)
            newBloomFilter(key,newFunnel(),DEFAULT_MAX_SIZE, DEFAULT_FPP);

        bloomFilterMap.get(key).put(value);
    }
    /**
     * @date: 2024/7/11
     * @description: 直接使用当前对象类作为 key 和 value
     */
    public static <T> boolean mightContain(T value){
        if(value == null)
            throw new IllegalArgumentException("value 不允许为 null ");
        BloomFilter filter = bloomFilterMap.get(value.getClass());
        return (filter != null && filter.mightContain(value));
    }
    /**
     * @date: 2024/7/11
     * @description: 指定 key 类， value 值
     */
    public static <T> boolean mightContain(Class<?> key, T value){
        if(value == null)
            throw new IllegalArgumentException("value 不允许为 null ");
        BloomFilter filter = bloomFilterMap.get(key);
        return (filter != null && filter.mightContain(value));
    }
}
